package de.uni_bremen.exceptions.oops;

import java.util.LinkedList;

/**
 * Die Klasse repräsentiert eine Methode im Syntaxbaum.
 */
class MethodDeclaration extends Declaration
{
	/** Die lokale Variable SELF. */
	VarDeclaration				self		= new VarDeclaration(new Identifier("_self", null), false, false);
	
	/** Die lokale Variable BASE. */
	VarDeclaration				base		= new VarDeclaration(new Identifier("_base", null), false, false);
	
	/** Die erwarteten Parameter dieser Klasse */
	LinkedList<VarDeclaration>	params		= new LinkedList<VarDeclaration>();
	
	/** Die lokalen Variablen der Methode. */
	LinkedList<VarDeclaration>	vars		= new LinkedList<VarDeclaration>();
	
	/** Die Anweisungen der Methode, d.h. der Methodenrumpf. */
	LinkedList<Statement>		statements	= new LinkedList<Statement>();
	
	/** Index in der VMT */
	int							index;
	
	/** Der Rückgabetyp der Methode. */
	ResolvableIdentifier		type;
	
	/**
	 * Konstruktor.
	 * 
	 * @param identifier
	 *            Der Name der deklarierten Methode.
	 */
	MethodDeclaration(Identifier name)
	{
		super(name);
	}
	
	/**
	 * Führt die Kontextanalyse für diese Methoden-Deklaration durch.
	 * 
	 * @param declarations
	 *            Die an dieser Stelle gültigen Deklarationen.
	 * @throws CompileException
	 *             Während der Kontextanylyse wurde ein Fehler gefunden.
	 */
	void contextAnalysis(Declarations declarations) throws CompileException
	{
		contextAnalysis(declarations, false);
	}
	
	/**
	 * Führt die Kontextanalyse für diese Methoden-Deklaration durch.
	 * 
	 * @param declarations
	 *            Die an dieser Stelle gültigen Deklarationen.
	 * @param firstPass
	 *            true: Löst Typen der Variablen, Parameter und des Rückgabewertes aus.<br>
	 *            false: Kontextanalyse für die Methode.
	 * @throws CompileException
	 *             Während der Kontextanylyse wurde ein Fehler gefunden.
	 */
	void contextAnalysis(Declarations declarations, boolean firstPass) throws CompileException
	{
		if (firstPass)
		{
			// SELF ist Variable vom Typ dieser Klasse
			self.type = new ResolvableIdentifier(declarations.currentClass.identifier.name, null);
			self.type.declaration = declarations.currentClass;
			
			// BASE ist Variable vom Typ der Basisklasse dieser Klasse
			base.type = new ResolvableIdentifier(declarations.currentClass.baseClass.identifier.name, null);
			base.type.declaration = declarations.currentClass.baseClass;
			
			// Löse Typen aller Parameter auf
			for (VarDeclaration p : params)
			{
				p.contextAnalysis(declarations);
			}
			
			// Löse Typ der Rückgabe aus
			if (type != null)
			{
				declarations.resolveType(type);
			}
			
			// Löse Typen aller Variablen auf
			for (VarDeclaration v : vars)
			{
				v.contextAnalysis(declarations);
			}
		}
		else
		{
			// Neuen Deklarationsraum schaffen
			declarations.enter();
			declarations.currentMethod = this;
			
			// SELF eintragen
			declarations.add(self);
			
			// BASE eintragen
			declarations.add(base);
			
			// SELF liegt vor der Rücksprungadresse auf dem Stapel
			self.offset = -2 - params.size();
			
			// BASE liegt vor der Rücksprungadresse auf dem Stapel
			base.offset = -2 - params.size();
			
			int offset = self.offset + 1;
			
			// Übergebene Parameter eintragen
			for (VarDeclaration p : params)
			{
				declarations.add(p);
				p.offset = offset++;
			}
			
			// Rücksprungadresse und alten Rahmenzeiger überspringen
			offset += 2;
			
			// Lokale Variablen eintragen
			for (VarDeclaration v : vars)
			{
				declarations.add(v);
				v.offset = offset++;
			}
			
			boolean hasReturn = false;
			// Kontextanalyse aller Anweisungen durchführen
			for (Statement s : statements)
			{
				s.contextAnalysis(declarations);
				hasReturn = hasReturn || s.returns();
			}
			if (!hasReturn)
				// Wenn kein RETURN vorhanden und Rückgabe-Typ void, RETURN einfügen
				if (type == null)
				{
					Statement s = new ReturnStatement();
					statements.add(s);
					s.contextAnalysis(declarations);
				}
				else
				{
					throw new CompileException("Diese Methode erwartet eine Rückgabe des Typs " + type.declaration,
							identifier.position);
				}
			else
				if (identifier.name.equals("main"))
					if (type != null)
						throw new CompileException("Main darf keinen Rückgabetyp haben", identifier.position);
			
			// Alten Deklarationsraum wiederherstellen
			declarations.leave();
		}
	}
	
	/**
	 * Die Methode gibt diese Deklaration in einer Baumstruktur aus.
	 * 
	 * @param tree
	 *            Der Strom, in den die Ausgabe erfolgt.
	 */
	void print(TreeStream tree)
	{
		tree.println("METHOD " + identifier.name);
		tree.indent();
		if (!vars.isEmpty())
		{
			tree.println("VARIABLES");
			tree.indent();
			for (VarDeclaration v : vars)
			{
				v.print(tree);
			}
			tree.unindent();
		}
		if (!statements.isEmpty())
		{
			tree.println("BEGIN");
			tree.indent();
			for (Statement s : statements)
			{
				s.print(tree);
			}
			tree.unindent();
		}
		tree.unindent();
	}
	
	String getLabel()
	{
		return self.type.name + "_" + identifier.name;
	}
	
	/**
	 * Generiert den Assembler-Code für diese Methode. Dabei wird davon ausgegangen, dass die Kontextanalyse vorher
	 * erfolgreich abgeschlossen wurde.
	 * 
	 * @param code
	 *            Der Strom, in den die Ausgabe erfolgt.
	 */
	void generateCode(CodeStream code)
	{
		code.setNamespace(self.type.name + "_" + identifier.name);
		code.println("; METHOD " + identifier.name);
		code.println(getLabel() + ":");
		code.println("ADD R2, R1");
		code.println("MMR (R2), R3 ; Alten Stapelrahmen sichern");
		code.println("MRR R3, R2 ; Aktuelle Stapelposition ist neuer Rahmen");
		if (!vars.isEmpty())
		{
			code.println("MRI R5, " + vars.size());
			code.println("ADD R2, R5 ; Platz für lokale Variablen schaffen");
		}
		for (Statement s : statements)
		{
			s.generateCode(code);
		}
	}
	
	/**
	 * Generiert den Assembler-Code für die Rückgabe dieser Methode. Dabei wird davon ausgegangen, dass die
	 * Kontextanalyse vorher erfolgreich abgeschlossen wurde.
	 * 
	 * @param code
	 *            Der Strom, in den die Ausgabe erfolgt.
	 * @param e
	 *            RETURN-Expression oder null
	 */
	void generateExitCode(CodeStream code, Expression e)
	{
		code.println("; END METHOD " + identifier.name);
		if (e != null)
		{
			code.println("; RETURN");
			e.generateCode(code);
			code.println("MRM R7, (R2) ; Ergebnis zwischenspeichern");
		}
		code.println("MRI R5, " + (vars.size() + params.size() + 3));
		code.println("SUB R2, R5 ; Stack korrigieren");
		if (e != null)
			code.println("MMR (R2), R7 ; RETURN auf den Stack");
		code.println("SUB R3, R1");
		code.println("MRM R5, (R3) ; Rücksprungadresse holen");
		code.println("ADD R3, R1");
		code.println("MRM R3, (R3) ; Alten Stapelrahmen holen");
		code.println("MRR R0, R5 ; Rücksprung");
	}
	
	/**
	 * Die Methode gibt den Namen dieser Methode in Textform aus.
	 */
	@Override
	public String toString()
	{
		return "METHOD " + identifier.name;
	}
	
	/**
	 * Prüft, ob die Signaturen der Methode übereinstimmen.
	 * 
	 * @param obj
	 *            Eine MethodDeclaration, mit der verglichen werden soll.
	 */
	@Override
	public boolean equals(Object obj)
	{
		if (obj instanceof MethodDeclaration)
		{
			MethodDeclaration m = (MethodDeclaration) obj;
			boolean sameReturn = true;// type.declaration == m.type.declaration; // TODO return-type
			boolean sameParameters = true;
			int i = 0;
			for (VarDeclaration v : params)
				if (m.params.get(i++).type.declaration != v.type.declaration)
				{
					sameParameters = false;
					break;
				}
			sameParameters = params.size() == m.params.size() && sameParameters;
			return ((MethodDeclaration) obj).identifier.equals(identifier) && sameReturn && sameParameters;
		}
		return super.equals(obj);
	}
}
